// Copyright 2009-2025 NTESS. Under the terms
// of Contract DE-NA0003525 with NTESS, the U.S.
// Government retains certain rights in this software.
//
// Copyright (c) 2009-2025, NTESS
// All rights reserved.
//
// This file is part of the SST software package. For license
// information, see the LICENSE file in the top level directory of the
// distribution.

#ifndef SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H
#define SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H

#ifndef SST_INCLUDING_SERIALIZE_H
#warning \
    "The header file sst/core/serialization/impl/serialize_variant.h should not be directly included as it is not part of the stable public API.  The file is included in sst/core/serialization/serialize.h"
#endif

#include "sst/core/serialization/serializer.h"

#include <array>
#include <utility>
#include <variant>

namespace SST::Core::Serialization {

// Serialize std::variant
template <typename... Types>
class serialize_impl<std::variant<Types...>>
{
    void operator()(std::variant<Types...>& obj, serializer& ser, ser_opt_t UNUSED(options))
    {
        size_t index = std::variant_npos;
        switch ( ser.mode() ) {
        case serializer::SIZER:
            index = obj.index();
            ser.size(index);
            break;

        case serializer::PACK:
            index = obj.index();
            ser.pack(index);
            break;

        case serializer::UNPACK:
            ser.unpack(index);

            // Set the active variant to index. The variant must be default-constructible.
            // We cannot portably restore valueless_by_exception but we do nothing in that case.
            if ( index != std::variant_npos ) set_index<std::index_sequence_for<Types...>>::array.at(index)(obj);
            break;

        case serializer::MAP:
        {
            // TODO -- how to handle mapping of std::variant ?
            return;
        }
        }

        // std::visit instantiates the generic lambda [&](auto& x) { SST_SER(x); } for each variant
        // Types... in obj, and calls the one corresponding to obj's currently held variant

        // Serialize the currently active variant if it's not valueless_by_exception
        if ( index != std::variant_npos ) std::visit([&](auto& x) { SST_SER(x); }, obj);
    }

    // Table of functions to set the active variant
    //
    // This defines a partial specialization of the set_index<std::variant<Types...>> struct template which takes a
    // single "type" template argument. This specialization matches std::index_sequence<INDEX...> representing a
    // sequence of subscripts 0, 1, 2, ..., sizeof...(Types)-1 generated by std::index_sequence_for<Types...>
    //
    // This set_index partial specialization has a constexpr array of function pointers initialized by a list of lambda
    // expressions, each of which takes a std::variant<Types...>& obj argument and calls obj.emplace<INDEX>() which
    // changes the active variant of obj to the Types... entry corresponding to INDEX. The emplace<INDEX>() method is
    // called with no constructor arguments, which means that the new active variant will be default-constructed. This
    // changing of the active variant happens before the new variant is deserialized in-place.
    //
    // Although std::visit can invoke a function with the currently active std::variant, it relies on knowing the
    // currently active variant as returned by index(). To CHANGE the currently active variant requires an assignment
    // or swap of the std::variant object or a call to the emplace() method.
    //
    // Changing the currently active variant to INDEX by calling emplace<INDEX>() requires that INDEX be a compile-time
    // constant, and the effects of constructing the newly active variant are different for each of the Types... indexed
    // by INDEX, and so a separate function table entry is needed for each INDEX so that the correct one can be
    // dispatched at runtime.
    //
    // { obj.emplace<0>(), obj.emplace<1>(), obj.emplace<2>(), ..., obj.emplace<sizeof...(Types)-1>() }

    template <typename>
    struct set_index;

    template <size_t... INDEX>
    struct set_index<std::index_sequence<INDEX...>>
    {
        static constexpr std::array<void (*)(std::variant<Types...>& obj), sizeof...(INDEX)> array = {
            [](std::variant<Types...>& obj) { obj.template emplace<INDEX>(); }...
        };
    };

    SST_FRIEND_SERIALIZE();
};

} // namespace SST::Core::Serialization

#endif // SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H
